﻿/************************************************************************
 * author:漠然 2013年7月12日4:25:15
 * **********************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Web;
using System.Linq;
using System.Text.RegularExpressions;
using MuYue.Log;

namespace MuYue.Http
{
    /// <summary>
    /// Http下载类
    /// </summary>
    public static class WebEngine
    {
        /// <summary>
        /// IP地址缓存
        /// </summary>
        private static readonly Dictionary<string, IPAddress> _dns = new Dictionary<string, IPAddress>();

        /// <summary>
        /// 从网页直接获取返回字符串
        /// </summary>
        /// <param name="url">网址</param>
        /// <returns>网页中的内容</returns>
        public static string DownloadString(string url) {
            return DownloadString(new Uri(!url.StartsWith("http://") ? string.Format("http://{0}", url) : url));
        }

        /// <summary>
        /// 自动网页捕获编码
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        public static Encoding GetEncoding(string html) {
            string pattern = @"(?i)\bcharset=(?<charset>[-a-zA-Z_0-9]+)";
            string charset = Regex.Match(html, pattern, RegexOptions.IgnoreCase).Groups["charset"].Value;
            try {
                return Encoding.GetEncoding(charset);
            }
            catch (ArgumentException) {
                return null;
            }
        }

        /// <summary>
        /// 乱码判断
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        static bool IsLuan(string html) {
            var bytes = Encoding.UTF8.GetBytes(html);
            //239 191 189
            for (var i = 0; i < bytes.Length; i++) {
                if (i < bytes.Length - 3)
                    if (bytes[i] == 239 && bytes[i + 1] == 191 && bytes[i + 2] == 189) {
                        return true;
                    }
            }
            return false;
        }



        /// <summary>
        /// Json格式访问api
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static T JsonApi<T>(string url) where T : class {
            string result;
            var uri = new Uri(url);
            using (var ns = RequestWeb(uri)) {
                using (var ms = new MemoryStream()) {
                    var encoding1 = ns.GetResponse(ms);
                    var buffer = ms.ToArray();
                    var encoding = encoding1 ?? Encoding.UTF8;
                    result = encoding.GetString(buffer);
                    if (encoding1 == null) {
                        //根据Meta来获取编码
                        encoding = GetEncoding(result);
                        if (encoding != null) {
                            if (encoding != Encoding.UTF8) {
                                result = encoding.GetString(buffer);
                            }
                        }
                        else if (IsLuan(result)) {
                            //如果Meta也没有捕获编码 就使用自动乱码判定
                            result = Encoding.Default.GetString(buffer);
                        }
                    }
                }
            }
            if (string.IsNullOrWhiteSpace(result)) {
                throw new Exception("访问网络获取数据失败了");
            }
            var r = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(result);
            return r;
        }

        /// <summary>
        /// 使用uri的方式获取网页内容
        /// </summary>
        /// <param name="uri">网页uri</param>
        /// <returns>网页内容</returns>
        public static string DownloadString(Uri uri) {
            string result;
            try {
                using (var ns = RequestWeb(uri)) {
                    using (var ms = new MemoryStream()) {
                        var encoding1 = ns.GetResponse(ms);
                        var buffer = ms.ToArray();
                        var encoding = encoding1 ?? Encoding.UTF8;
                        result = encoding.GetString(buffer);
                        if (encoding1 == null) {
                            //根据Meta来获取编码
                            encoding = GetEncoding(result);
                            if (encoding != null) {
                                if (encoding != Encoding.UTF8) {
                                    result = encoding.GetString(buffer);
                                }
                            }
                            else if (IsLuan(result)) {
                                //如果Meta也没有捕获编码 就使用自动乱码判定
                                result = Encoding.Default.GetString(buffer);
                            }
                        }
                    }
                }
            }
            catch (Exception ex) {
                result = ex.Message;
            }
            return result;
        }

        /// <summary>
        /// 获取ip
        /// 1.从dns内部缓存中获取
        /// 2.使用DNS类进行尝试获取
        /// </summary>
        /// <param name="host"></param>
        /// <returns></returns>
        private static IPAddress GetIp(string host) {
            host = host.ToLower();
            IPAddress result;
            if (!_dns.TryGetValue(host, out result)) {
                try {
                    var addrList = Dns.GetHostAddresses(host);
                    result = addrList[0];
                }
                catch (Exception ex) {
                    LogService.Write(ex.Message, MuYue.Log.LogLevel.错误);
                }
                _dns.Add(host, result);
            }
            return result;
        }

        /// <summary>
        /// 从网页下载二进制字节码
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static byte[] DownloadData(string url) {
            return DownloadData(new Uri(url));
        }

        /// <summary>
        /// 发送HTTP请求数据给服务器
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static NetworkStream RequestWeb(Uri uri) {
            //using (Profiler.Step("连接"))
            //{
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            var ip = GetIp(uri.DnsSafeHost);
            if (ip == null)
                throw new Exception("获取DNS失败");
            socket.Connect(ip, uri.Port);
            var ns = new NetworkStream(socket, true);
            ns.Write("GET {0} HTTP/1.1\r\nAccept-Charset:utf-8,gb2312\r\nHOST:{1}\r\n\r\n", uri.PathAndQuery, uri.Host);
            return ns;
            //}
        }

        /// <summary>
        /// 从网页下载二进制字节码
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static byte[] DownloadData(Uri uri) {
            try {
                using (var ns = RequestWeb(uri)) {
                    using (var ms = new MemoryStream()) {
                        ns.GetResponse(ms);
                        return ms.ToArray();
                    }
                }
            }
            catch {
                return null;
            }
        }

        /// <summary>
        /// 从网页下载文件
        /// </summary>
        /// <param name="url"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static bool DownloadFile(string url, string fileName) {
            return DownloadFile(new Uri(url), fileName);
        }

        /// <summary>
        /// 从网页下载文件
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static bool DownloadFile(Uri uri, string fileName) {
            try {
                using (var ns = RequestWeb(uri)) {
                    using (var fs = File.Create(fileName)) {
                        ns.GetResponse(fs);
                        return true;
                    }
                }
            }
            catch {
                return false;
            }
        }
    }
}
